﻿#pragma encoding=UTF-8
#define MyAppVersion "2.1"

[Setup]
AppName=ScanOS Updater
AppVersion={#MyAppVersion}
DefaultDirName={pf}\ScanOS
DefaultGroupName=ScanOS System
DisableProgramGroupPage=auto
OutputDir=.\Output
OutputBaseFilename=ScanOS_Updater
Compression=lzma2
SolidCompression=yes
PrivilegesRequired=admin
SetupIconFile=.\Tray\icon.ico
WizardStyle=modern
SourceDir=.
AllowNoIcons=yes
ArchitecturesAllowed=x86 x64
ArchitecturesInstallIn64BitMode=x64
SetupLogging=yes
AppId={{ScanOS-Updater-{#MyAppVersion}}
RestartIfNeededByRun=False
UsePreviousAppDir=yes
DisableDirPage=auto
VersionInfoVersion={#MyAppVersion}.0
SetupMutex=ScanOSUpdaterMutex
CloseApplications=no

[Messages]
RestartManagerTitle=需要关闭应用程序
RestartManagerMessage=以下应用程序正在使用需要更新的文件:%n%n%1%n%n是否尝试关闭它们?

[Files]
; 应用程序文件 - 只包含App、Scripts和Tray
Source: "App\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs
Source: "Tray\*"; DestDir: "{app}\Tray"; Flags: ignoreversion
Source: "Scripts\install-service.bat"; DestDir: "{app}"; Flags: ignoreversion
Source: "Scripts\launch-tray.cmd"; DestDir: "{app}\Tray"; Flags: ignoreversion
Source: "Scripts\refresh_env.cmd"; DestDir: "{app}"; Flags: ignoreversion

[Icons]
Name: "{group}\Control Panel"; Filename: "{app}\Tray\launch-tray.cmd"; IconFilename: "{app}\Tray\icon.ico"
Name: "{group}\Service Manager"; Filename: "services.msc"; IconFilename: "imageres.dll,-25"
Name: "{group}\Uninstall"; Filename: "{uninstallexe}"
Name: "{commondesktop}\ScanOS Control"; Filename: "{app}\Tray\launch-tray.cmd"; IconFilename: "{app}\Tray\icon.ico"; Tasks: desktopicon; Comment: "ScanOS Control Panel";

[Tasks]
Name: desktopicon; Description: "Create desktop icon"; GroupDescription: "Additional icons:"; 
Name: starttray; Description: "Launch ScanOS Control Panel"; GroupDescription: "Launch options:"; Flags: checkedonce

[Run]
Filename: "{app}\Tray\launch-tray.cmd"; Description: "Start ScanOS Control Panel"; Flags: nowait postinstall skipifsilent runascurrentuser; Tasks: starttray

[UninstallRun]
Filename: "{cmd}"; Parameters: "/c taskkill /f /im javaw.exe /fi ""WINDOWTITLE eq ScanOS Control"""; Flags: runhidden; RunOnceId: "KillTray"
Filename: "{cmd}"; Parameters: "/c net stop ScanOSService & ""{app}\ScanOSService.exe"" uninstall & sc delete ScanOSService"; Flags: runhidden; RunOnceId: "UninstallService"

[Code]
const
  ERROR_ALREADY_EXISTS = 183;

// 声明Windows API函数
function CreateMutex(lpMutexAttributes: Longint; bInitialOwner: Longint; lpName: string): Longint;
  external 'CreateMutexA@kernel32.dll stdcall';
function CloseHandle(hObject: Longint): Boolean;
  external 'CloseHandle@kernel32.dll stdcall';
function GetLastError: Longint;
  external 'GetLastError@kernel32.dll stdcall';

var
  ServiceInstalled: Boolean;
  StartTrayAfterInstall: Boolean;
  IsUpgrade: Boolean;
  PrevAppDir: string;
  InstalledVersion: string;
  MutexHandle: Longint;
  MyAppVersionStr: string;

function InitializeSetup(): Boolean;
begin
  MutexHandle := CreateMutex(0, 1, 'ScanOSUpdaterMutex');
  if (MutexHandle = 0) or (GetLastError() = ERROR_ALREADY_EXISTS) then
  begin
    MsgBox('更新程序已在运行中。', mbError, MB_OK);
    Result := False;
    Exit;
  end;
  
  Log('Updater initialization started');
  Log('Start time: ' + GetDateTimeString('yyyy/mm/dd hh:nn:ss', '-', ':'));
  
  // 初始化版本号字符串
  MyAppVersionStr := '2.1';
  
  // 检查已安装版本
  if RegQueryStringValue(HKLM, 'Software\Microsoft\Windows\CurrentVersion\Uninstall\' + 
             '{ScanOS-Updater-1.0}_is1',
             'DisplayVersion', InstalledVersion) then
  begin
    IsUpgrade := True;
    Log('Previous installation found. Version: ' + InstalledVersion);
  end
  else if RegQueryStringValue(HKLM, 'Software\Microsoft\Windows\CurrentVersion\Uninstall\' + 
             '{ScanOS-1.0}_is1',
             'DisplayVersion', InstalledVersion) then
  begin
    IsUpgrade := True;
    Log('Previous ScanOS installation found. Version: ' + InstalledVersion);
  end
  else
  begin
    IsUpgrade := False;
    InstalledVersion := '';
  end;
  
  Result := True;
end;

procedure DeinitializeSetup();
begin
  if MutexHandle <> 0 then
    CloseHandle(MutexHandle);
end;

function BoolToStr(Value: Boolean): String;
begin
  if Value then Result := 'True' else Result := 'False';
end;

function StopProcessEx(ProcessName: string; MaxRetries: Integer): Boolean;
var
  ResultCode: Integer;
  RetryCount: Integer;
begin
  Result := False;
  RetryCount := 1;
  
  while RetryCount <= MaxRetries do
  begin
    Log('Stopping process: ' + ProcessName + ' attempt ' + IntToStr(RetryCount));
    
    if Exec('taskkill', '/f /im ' + ProcessName, '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then
    begin
      if ResultCode = 0 then
      begin
        Result := True;
        Log(ProcessName + ' stopped successfully');
        Break;
      end;
    end;
    
    if not Result then
    begin
      if Exec('wmic', 'process where name="' + ProcessName + '" delete', '',
              SW_HIDE, ewWaitUntilTerminated, ResultCode) and (ResultCode = 0) then
      begin
        Result := True;
        Log(ProcessName + ' stopped using WMIC');
        Break;
      end;
    end;
    
    Sleep(2000);
    RetryCount := RetryCount + 1;
  end;
end;

function StopServiceWithRetry(ServiceName: string; MaxRetries: Integer): Boolean;
var
  ResultCode, RetryCount: Integer;
begin
  Result := False;
  RetryCount := 1;
  
  while RetryCount <= MaxRetries do
  begin
    Log('Stop service attempt ' + IntToStr(RetryCount) + '/' + IntToStr(MaxRetries));
    
    if Exec('net', 'stop ' + ServiceName, '', SW_HIDE, ewWaitUntilTerminated, ResultCode) and (ResultCode = 0) then
    begin
      Result := True;
      Break;
    end;
    
    if Exec('sc', 'stop ' + ServiceName, '', SW_HIDE, ewWaitUntilTerminated, ResultCode) and (ResultCode = 0) then
    begin
      Result := True;
      Break;
    end;
    
    if StopProcessEx('ScanOSService.exe', 1) then
    begin
      Result := True;
      Break;
    end;
    
    Sleep(2000);
    RetryCount := RetryCount + 1;
  end;
end;

function UninstallService(ServiceName: string): Boolean;
var
  ResultCode: Integer;
begin
  Result := False;
  
  if FileExists(PrevAppDir + '\ScanOSService.exe') then
  begin
    if Exec(PrevAppDir + '\ScanOSService.exe', 'uninstall', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) and (ResultCode = 0) then
    begin
      Result := True;
      Log('Service uninstalled using ScanOSService.exe');
    end;
  end;
  
  if not Result then
  begin
    if Exec('sc', 'delete ' + ServiceName, '', SW_HIDE, ewWaitUntilTerminated, ResultCode) and (ResultCode = 0) then
    begin
      Result := True;
      Log('Service deleted using sc command');
    end;
  end;
  
  if not Result then
  begin
    if Exec(ExpandConstant('{sys}\sc.exe'), 'delete ' + ServiceName, '', SW_HIDE, ewWaitUntilTerminated, ResultCode) and (ResultCode = 0) then
    begin
      Result := True;
      Log('Service deleted using system sc.exe');
    end;
  end;
end;

function StopTrayProgram(): Boolean;
begin
  Result := StopProcessEx('javaw.exe', 3);
end;

procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
var
  ResultCode: Integer;
  AppDataDir: string;
begin
  case CurUninstallStep of
    usUninstall:
    begin
      Log('===== Uninstall process started =====');
      
      if not StopProcessEx('javaw.exe', 3) then
      begin
        Log('Failed to stop tray program');
      end;
      
      if not StopServiceWithRetry('ScanOSService', 3) then
      begin
        Log('Failed to stop service');
      end;
      
      Log('Deleting service registration');
      if not UninstallService('ScanOSService') then
      begin
        Log('Service uninstall failed');
      end;
      
      Sleep(1000);
      
      Log('Cleaning files and directories...');
      
      if DirExists(ExpandConstant('{app}')) then
      begin
        DelTree(ExpandConstant('{app}'), True, True, True);
      end;
      
      AppDataDir := ExpandConstant('{localappdata}\ScanOS');
      if DirExists(AppDataDir) then
      begin
        DelTree(AppDataDir, True, True, True);
      end;
      
      AppDataDir := ExpandConstant('{localappdata}\ScanOS_Logs');
      if DirExists(AppDataDir) then
      begin
        DelTree(AppDataDir, True, True, True);
      end;
      
      Log('===== Uninstall completed =====');
    end;
  end;
end;

// 自定义服务存在检查函数
function ServiceExists(ServiceName: string): Boolean;
var
  ResultCode: Integer;
begin
  Result := Exec('cmd.exe', '/c sc query ' + ServiceName + ' > nul 2>&1', '', 
                 SW_HIDE, ewWaitUntilTerminated, ResultCode) and (ResultCode = 0);
end;

// 自定义服务运行状态检查函数
function ServiceRunning(ServiceName: string): Boolean;
var
  ResultCode: Integer;
begin
  Result := Exec('cmd.exe', '/c sc query ' + ServiceName + ' | find "RUNNING" > nul', '', 
                 SW_HIDE, ewWaitUntilTerminated, ResultCode) and (ResultCode = 0);
end;

function GetHKLM: Integer;
begin
  if Is64BitInstallMode then
    Result := HKLM64
  else
    Result := HKLM32;
end;

// 版本比较函数
function CompareVersions(Version1, Version2: string): Integer;
var
  Pos1, Pos2: Integer;
  Num1, Num2: Integer;
  Part1, Part2: string;
begin
  // 循环比较每个部分
  while (Version1 <> '') or (Version2 <> '') do
  begin
    // 获取第一个版本号的下一部分
    Pos1 := Pos('.', Version1);
    if Pos1 = 0 then
    begin
      Part1 := Version1;
      Version1 := '';
    end
    else
    begin
      Part1 := Copy(Version1, 1, Pos1 - 1);
      Delete(Version1, 1, Pos1);
    end;
    
    // 获取第二个版本号的下一部分
    Pos2 := Pos('.', Version2);
    if Pos2 = 0 then
    begin
      Part2 := Version2;
      Version2 := '';
    end
    else
    begin
      Part2 := Copy(Version2, 1, Pos2 - 1);
      Delete(Version2, 1, Pos2);
    end;
    
    // 将部分转换为数字
    Num1 := StrToIntDef(Part1, 0);
    Num2 := StrToIntDef(Part2, 0);
    
    // 比较数字部分
    if Num1 > Num2 then
    begin
      Result := 1;
      Exit;
    end
    else if Num1 < Num2 then
    begin
      Result := -1;
      Exit;
    end;
  end;
  
  // 所有部分都相同
  Result := 0;
end;

// 版本检查函数
function CheckVersion: Boolean;
var
  CompareResult: Integer;
begin
  Result := True;
  
  if IsUpgrade and (InstalledVersion <> '') then
  begin
    CompareResult := CompareVersions(InstalledVersion, MyAppVersionStr);
    
    if CompareResult = 0 then
    begin
      // 版本相同
      if MsgBox('已安装相同版本 (' + InstalledVersion + ')，是否继续重新安装?', 
          mbConfirmation, MB_YESNO) = IDNO then
      begin
        Result := False;
      end;
    end
    else if CompareResult > 0 then
    begin
      // 已安装版本更高
      if MsgBox('警告：已安装版本 (' + InstalledVersion + ') 比当前版本 (' + MyAppVersionStr + ') 高。' + #13#10 +
                '继续安装将降级到旧版本，是否继续?',
                mbConfirmation, MB_YESNO) = IDNO then
      begin
        Result := False;
      end;
    end
    else
    begin
      // 当前版本更高 - 正常更新情况
      Log('执行版本更新: ' + InstalledVersion + ' -> ' + MyAppVersionStr);
    end;
  end;
end;

function PrepareToInstall(var NeedsRestart: Boolean): string;
begin
  Result := '';
  Log('准备更新阶段开始');
  
  // 检查是否有正在运行的ScanOS进程
  if not StopTrayProgram() then
  begin
    Log('停止托盘程序失败，继续更新');
  end;
  
  if not StopServiceWithRetry('ScanOSService', 3) then
  begin
    Log('停止服务失败，强制终止进程');
    StopProcessEx('ScanOSService.exe', 3);
  end;
  
  Sleep(1000);
  
  Log('准备更新阶段结束');
end;

// 服务安装过程
procedure InstallService;
var
  ResultCode: Integer;
  AppDir, JavaHome, ServiceExe, ServiceXml, JavaHomeValue, XmlContent: string;
  JavaHomeFound: Boolean;
begin
  AppDir := ExpandConstant('{app}');
  ServiceExe := AppDir + '\ScanOSService.exe';
  
  Log('开始服务更新流程');
  
  // 1. 停止并删除现有服务
  Log('停止现有服务...');
  if not StopServiceWithRetry('ScanOSService', 3) then
  begin
    Log('停止服务失败，尝试强制终止进程');
    StopProcessEx('ScanOSService.exe', 3);
  end;
  
  if not UninstallService('ScanOSService') then
  begin
    Log('服务卸载失败');
  end;
  
  // 2. 获取JAVA_HOME路径
  JavaHomeFound := False;
  if RegQueryStringValue(HKLM, 'SOFTWARE\ScanOS', 'JavaHome', JavaHome) then
  begin
    JavaHomeValue := JavaHome;
    JavaHomeFound := True;
    Log('从注册表找到JavaHome: ' + JavaHomeValue);
  end
  else if RegQueryStringValue(HKLM, 'SOFTWARE\JavaSoft\Java Development Kit\17', 'JavaHome', JavaHome) then
  begin
    JavaHomeValue := JavaHome;
    JavaHomeFound := True;
    Log('从JDK注册表找到JavaHome: ' + JavaHomeValue);
  end;
  
  if not JavaHomeFound then
  begin
    if DirExists('C:\Program Files\Eclipse Adoptium\jdk-17.0.14.7-hotspot') then
    begin
      JavaHomeValue := 'C:\Program Files\Eclipse Adoptium\jdk-17.0.14.7-hotspot';
      JavaHomeFound := True;
      Log('使用默认JavaHome路径: ' + JavaHomeValue);
    end
    else if DirExists('C:\Program Files\Java\jdk-17') then
    begin
      JavaHomeValue := 'C:\Program Files\Java\jdk-17';
      JavaHomeFound := True;
      Log('使用默认JavaHome路径: ' + JavaHomeValue);
    end;
  end;
  
  if not JavaHomeFound then
  begin
    MsgBox('警告：未找到Java运行时，服务可能无法正常启动', mbInformation, MB_OK);
    JavaHomeValue := '';
  end;
  
  // 3. 生成XML配置文件
  ServiceXml := AppDir + '\ScanOSService.xml';
  
  // 正确构建XML内容
  XmlContent := 
    '<?xml version="1.0" encoding="UTF-8"?>' + #13#10 +
    '<service>' + #13#10 +
    '  <id>ScanOSService</id>' + #13#10 +
    '  <name>ScanOS Service</name>' + #13#10 +
    '  <description>ScanOS Background Service</description>' + #13#10 +
    '  <executable>cmd.exe</executable>' + #13#10 +
    '  <arguments>/c ""' + AppDir + '\start-lib-app-server.bat""</arguments>' + #13#10 +
    '  <workingdirectory>' + AppDir + '</workingdirectory>' + #13#10 +
    '  <logpath>' + AppDir + '\Logs</logpath>' + #13#10 +
    '  <logmode>rotate</logmode>' + #13#10 +
    '  <onfailure action="restart" delay="10 sec"/>' + #13#10 +
    '  <startmode>Automatic</startmode>';
  
  if JavaHomeValue <> '' then
  begin
    XmlContent := XmlContent + #13#10 +
      '  <env name="PATH" value="%PATH%;' + JavaHomeValue + '\bin" />' + #13#10 +
      '  <env name="JAVA_HOME" value="' + JavaHomeValue + '" />';
  end;
  
  XmlContent := XmlContent + #13#10 + '</service>';
  
  // 保存XML内容
  SaveStringToFile(ServiceXml, XmlContent, False);
  
  Log('服务配置文件已生成: ' + ServiceXml);
  
  // 4. 安装服务
  Log('安装服务...');
  if Exec(ServiceExe, 'install', AppDir, SW_SHOW, ewWaitUntilTerminated, ResultCode) then
  begin
    if ResultCode = 0 then
    begin
      Log('服务安装成功');
    end
    else
    begin
      Log('主安装方法失败，错误代码: ' + IntToStr(ResultCode));
      Log('尝试备选方案');
      
      // 备选安装方法
      if Exec('sc', 'create ScanOSService binPath= "' + ServiceExe + ' run" start= auto DisplayName= "ScanOS Service"', 
             '', SW_SHOW, ewWaitUntilTerminated, ResultCode) then
      begin
        if ResultCode = 0 then
          Log('备选安装方法成功')
        else
          Log('备选安装方法失败');
      end;
    end;
  end;
  
  // 5. 启动服务
  Log('启动服务...');
  if Exec('net', 'start ScanOSService', '', SW_SHOW, ewWaitUntilTerminated, ResultCode) then
  begin
    if ResultCode = 0 then
    begin
      Log('服务启动成功');
      ServiceInstalled := True;
    end
    else
    begin
      Log('服务启动失败，错误代码: ' + IntToStr(ResultCode));
      MsgBox('服务启动失败，请手动运行: net start ScanOSService', mbInformation, MB_OK);
    end;
  end;
end;

procedure CurStepChanged(CurStep: TSetupStep);
begin
  case CurStep of
    ssInstall:
    begin
      // 执行版本检查
      if not CheckVersion then
      begin
        Abort;
      end;
      
      Log('更新前准备开始');
      
      // 记录之前的安装目录
      if IsUpgrade then
      begin
        PrevAppDir := ExpandConstant('{app}');
        Log('Previous installation directory: ' + PrevAppDir);
      end;
      
      Log('更新前准备完成');
    end;
    
    ssPostInstall:
    begin
      Log('更新后步骤开始');
      InstallService;
    end;
  end;
end;

procedure CurPageChanged(CurPageID: Integer);
begin
  if CurPageID = wpFinished then
  begin
    StartTrayAfterInstall := IsTaskSelected('starttray');
    
    // 检查服务是否安装成功
    if not ServiceInstalled then
    begin
      if not ServiceExists('ScanOSService') then
      begin
        MsgBox(
          '服务安装失败，请手动运行: "' + ExpandConstant('{app}') + '\install-service.bat"' + #13#10 +
          '然后启动服务: net start ScanOSService',
          mbInformation, MB_OK
        );
      end
      else if not ServiceRunning('ScanOSService') then
      begin
        MsgBox(
          '服务已安装但未启动，请手动启动: net start ScanOSService',
          mbInformation, MB_OK
        );
      end;
    end;
  end;
end;